from time import localtime, strftime, time
from os import path, mkdir

_time = time()
from types import FunctionType


class AverageIter:
    def __init__(self) -> None:
        self.result = self.n = 0
        self.logInterval = 100

    @property
    def average(self) -> float:
        return self.result / self.n if self.n else 0

    def push(self, sample: float) -> None:
        self.result += sample
        self.n += 1
        if not self.n % self.logInterval:
            Logger.log(f"{self.average} every {self.logInterval} sample.")


class Logger:
    """简单的日志类"""

    file = "temp/log.txt"

    @staticmethod
    def log(*args) -> None:
        Logger.write(*args, flag='L')

    @staticmethod
    def warn(*args) -> None:
        Logger.write(*args, flag='W')

    @staticmethod
    def write(*args, flag: str) -> None:
        try:
            with open(Logger.file, 'a') as f:
                for i in args:
                    txt = f"{flag}|{Logger.now()} > {i}\n"
                    print(txt, end='')
                    f.write(txt)
        except FileNotFoundError as e:
            _dir = path.split(Logger.file)[0]
            if not path.exists(_dir):
                mkdir(_dir)
            with open(Logger.file, 'a') as f:
                f.write(f"E|{Logger.now()} > {e}\n")
                for i in args:
                    txt = f"{flag}|{Logger.now()} > {i}\n"
                    print(txt, end='')
                    f.write(txt)

    @staticmethod
    def now() -> str:
        return strftime('%Y.%m.%d_%H.%M.%S', localtime())


def confirmYes(tip: str) -> bool:
    return input(f"{tip} (y to confirm)").lower().strip() in ('y', 'yes')


class TicToc:
    __slots__ = "start", "ticList", "tocList", "cost"

    def __init__(self):
        self.start = True
        self.tocList, self.cost, self.ticList = [], [], [time()]

    def tic(self) -> None:
        if self.start:
            self.tocList.append(time())
            self.cost.append(self.tocList[-1] - self.ticList[-1])
        else:
            self.start = True
        self.ticList.append(time())

    def toc(self) -> float:
        if self.start:
            self.tocList.append(time())
            self.cost.append(self.tocList[-1] - self.ticList[-1])
            self.start = False
        return time() - self.ticList[-1]

    def __str__(self):
        return f"Totally cost {self.total():9.6f} / {len(self):6d}"

    def __len__(self):
        return len(self.cost)

    def read(self) -> None:
        print(''.join(f"历时 {t:9.6f} 秒。\n" for t in self.cost()))

    def total(self) -> float:
        return sum(self.cost)

    def beginning(self) -> float:
        return self.ticList[0] - _time

    def last(self) -> float:
        return (time() if self.start else self.tocList[-1]) - _time


class Timer:
    """计时器类"""

    start = _time
    current = None
    ticDict: "dict[str, TicToc]" = {None: TicToc()}

    @classmethod
    def tic(cls, key: str = None) -> None:
        cls.ticDict[cls.current].toc()
        cls.current = key
        if key in cls.ticDict:
            cls.ticDict[key].tic()
        else:
            cls.ticDict[key] = TicToc()

    @classmethod
    def total(cls) -> None:
        cls.ticDict[cls.current].toc()
        print(f"Ticking {cls.current} now.")
        totally = [
            (key, tictoc.total(), str(tictoc)) for key, tictoc in cls.ticDict.items()
        ]
        total = sum(map(lambda x: x[1], totally))
        totally.sort(key=lambda x: x[1])
        allTime = time() - cls.start
        for key, t, rep in totally:
            print(f"{rep} $ {t/allTime:6.2%} for {key}.")
        print(f"Totally cost {total:9.6f} $ {total/allTime:6.2%} for all recorded.")
        print(f"Totally cost {allTime:9.6f} for ALL.")

    @classmethod
    def timeIt(cls, key: str):
        def decorate(func):
            def timer(*args, **kwargs):
                current = cls.current
                cls.tic(key)
                ret = func(*args, **kwargs)
                cls.tic(current)
                return ret

            return timer

        return decorate

    @classmethod
    def timeItClass(cls, key: str):
        return lambda obj: type(
            obj.__name__,
            obj.__bases__,
            {
                k: cls.timeIt(key)(elem)
                if isinstance(elem, FunctionType)
                and not getattr(elem, "_is_taichi_function", False)
                else elem
                for k, elem in obj.__dict__.items()
                if k not in obj.__dict__.get("__slots__", ())
            },
        )
